package com.googlecode.silicoin.gen;

import static org.objectweb.asm.Opcodes.*;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

public class WrapClassLoader extends ClassLoader {
        private Random random = new Random(System.currentTimeMillis());

        public WrapClassLoader() {
                super(Thread.currentThread().getContextClassLoader());
        }

        public WrapClassLoader(ClassLoader parent) {
                super(parent);
        }

        Map<String, String> classNameMapTable = new HashMap<String, String>();

        public Class<?> generate(EntityDefinition entityDefinition) {

                if (entityDefinition == null || entityDefinition.getClassName() == null
                                || entityDefinition.getSuperClassName() == null) {
                        throw new java.lang.IllegalArgumentException(
                                        "wrong entityDefinition!");
                }

                String superClassName = entityDefinition.getSuperClassName();
                String subclazzName = entityDefinition.getClassName();
                if (entityDefinition.getVersion() != null) {
                        subclazzName += entityDefinition.getVersion();
                }
                String key = subclazzName;
                if (classNameMapTable.containsKey(key)) {
                        String s = classNameMapTable.get(key);
                        if (s != null) {
                                superClassName = s;
                        }
                }

                subclazzName = subclazzName + "$_AT_$" + System.currentTimeMillis()
                                + random.nextInt(99);
                classNameMapTable.put(key, subclazzName);

                String bytecodeClassName = transferClassName(superClassName);
                String bytecodeSubclassName = transferClassName(subclazzName);

                MethodVisitor mv;

                ClassWriter cw = new ClassWriter(false);
                cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, bytecodeSubclassName, null,
                                bytecodeClassName, null);

                {
                        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                        mv.visitCode();
                        mv.visitVarInsn(ALOAD, 0);
                        mv.visitMethodInsn(INVOKESPECIAL, bytecodeClassName, "<init>",
                                        "()V");
                        mv.visitInsn(RETURN);
                        mv.visitMaxs(1, 1);
                        mv.visitEnd();
                }

                Collection<PropertyDefinition> properties = entityDefinition
                                .getProperties();
                if (properties != null) {
                        String pName, stackFType, fName;
                        for (PropertyDefinition property : properties) {
                                fName = property.getName();
                                // constructor
                                stackFType = "L" + transferClassName(property.getType()) + ";";
                                cw.visitField(ACC_PRIVATE, fName, stackFType, null, null)
                                                .visitEnd();

                                pName = new StringBuffer(fName.substring(0, 1).toUpperCase()
                                                + (fName.length() > 1 ? fName.substring(1, fName
                                                                .length()) : "")).toString();
                                // getter
                                mv = cw.visitMethod(ACC_PUBLIC, "get" + pName, "()"
                                                + stackFType, null, null);
                                mv.visitCode();
                                mv.visitVarInsn(ALOAD, 0);
                                mv.visitFieldInsn(GETFIELD, bytecodeSubclassName, fName,
                                                stackFType);
                                mv.visitInsn(ARETURN);
                                mv.visitMaxs(1, 1);
                                mv.visitEnd();
                                // setter
                                mv = cw.visitMethod(ACC_PUBLIC, "set" + pName, "(" + stackFType
                                                + ")V", null, null);
                                mv.visitCode();
                                mv.visitVarInsn(ALOAD, 0);
                                mv.visitVarInsn(ALOAD, 1);
                                mv.visitFieldInsn(PUTFIELD, bytecodeSubclassName, fName,
                                                stackFType);
                                mv.visitMaxs(2, 2);
                                mv.visitInsn(RETURN);
                                mv.visitEnd();
                        }
                }

                cw.visitEnd();

                byte[] bytes = cw.toByteArray();

                Class<?> c1 = defineClass(subclazzName, bytes, 0, bytes.length);
                // this.resolveClass(c1);
                return c1;
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve)
                        throws ClassNotFoundException {
                if (classNameMapTable.containsKey(name)) {
                        String _name = classNameMapTable.get(name);
                        if (_name != null) {
                                return super.loadClass(_name, resolve);
                        }
                }
                return super.loadClass(name, resolve);
        }

        private static String transferClassName(String className) {
                return className.replace('.', '/');
        }
}
